/**
  ETFAna project, Anyang Normal University && IMP-CAS
  \class ETFPlaHit
  \brief for a DC hit, i.e. a fired sense wire.
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-10-29
  \date 2021-11-30 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date         <th>Author      <th>Description                   </tr>
  <tr>  <td>2021-10-29   <td>Asia Sun    <td>file created                  </tr>
  </table>

  \copyright Copyright (c) 2021-2024 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <cstring>
#include <TGraph.h>
#include "ETFPlaHit.h"
#include "ETFHPTDCCh.h"
#include "ETFAmpCh.h"
#include "ETFMsg.h"
#include "ETFMath.h"

#define within ETFMath::Within
#define er ETFMsg::Error

using std::cout;
using std::endl;

// EJ-232(Q), refelction index: n=1.58, veff=c/n=189.74206mm/ns
// (T1) 50mm/v = 263.5ps; (T0) 100mm/v = 527.0ps; (tofw) 1200mm/v = 6.3244ns
const double ETFPlaHit::VEFF = 189.74; // effective light velocity in pla: in mm/ns

// length 1200: for TOFWall strips
ETFPlaHit::ETFPlaHit() : ETFHit(), fLength(1200.), fUCh{0}, fDCh{0}, fQUCh(0),
fQDCh(0){
  Initialize();
} // end ctor

// copy ctor
ETFPlaHit::ETFPlaHit(const ETFPlaHit &t) : ETFHit(t){
  *this = t;
} // end copy ctor
// assignment ctor
ETFPlaHit &ETFPlaHit::operator=(const ETFPlaHit &t){
  if(&t == this) return *this;

  ETFHit::operator=(t);
  fLength = t.fLength;
  fStripId = t.fStripId;
  fDelay = t.fDelay;
  for(int i = 2; i--;){
    fUCh[i] = t.fUCh[i];
    fDCh[i] = t.fDCh[i];
  } // end for over i

  return *this;
} // end member function operator=

ETFPlaHit::~ETFPlaHit(){}

void ETFPlaHit::Initialize(){
  ETFHit::Initialize();
  fStripId = -1; fTrkId = -1;
  // need not delete memory - has been taken care of somewhere else
  for(int i = 2; i--;) fUCh[i] = fDCh[i] = nullptr;
  fQUCh = fQDCh = nullptr;
  fHitPos = -999.;
  fQ = fQL = fQR = -999.;
  fTime = fTimeL = fTimeR = -999.;
  fFiltered = false;
  fIsOptimal = false;
} // end member function Initialize

// \param udvh: 0-1-2-3: uv-uh-dv-dh
void ETFPlaHit::SetTChannel(const ETFRawChannel *ch, int udvh){
  // if(!ch) er("ETFPlaHit", "SetTChannel: ch is nullptr");
  auto tch = static_cast<const ETFHPTDCCh *>(ch);
  switch(udvh){
    case 0: fUCh[0] = tch; break; // UV
    case 1: fUCh[1] = tch; break; // UH
    case 2: fDCh[0] = tch; break; // DV
    case 3: fDCh[1] = tch; break; // DH
    default: er("ETFPlaHit", "SetTChannel: udvh is not 0~3"); break;
  } // end if
} // end member function SetTChannel

// \param udvh: 0-1-2-3: uv-uh-dv-dh
void ETFPlaHit::SetQChannel(const ETFRawChannel *ch, int ud){
  if(!ch) er("ETFPlaHit", "SetQChannel: ch is nullptr");
  auto ach = dynamic_cast<const ETFAmpCh *>(ch);
  switch(ud){
    case 4: fQUCh = ach; break; // UQ
    case 5: fQDCh = ach; break; // DQ
    default: er("ETFPlaHit", "SetQChannel: ud is not 4 or 5: %d", ud); break;
  } // end if
} // end member function SetQChannel

// udvh: [0-1-2-3]: [UV, UH, DV, DH]
const ETFHPTDCCh *ETFPlaHit::GetTChannel(int udvh) const{
  switch(udvh){
    case 0: return fUCh[0]; // UV
    case 1: return fUCh[1]; // UH
    case 2: return fDCh[0]; // DV
    case 3: return fDCh[1]; // DH
    default: er("ETFPlaHit", "GetTChannel:udvh is not 0~3"); break;
  } // end if
  return nullptr; // never gets here, just to suppress compiler warnings
} // end member function GetTChannel

// udvh: [0-1-2-3]: [UV, UH, DV, DH]
const ETFAmpCh *ETFPlaHit::GetQChannel(int ud) const{
  switch(ud){
    case 0: return fQUCh; // UQ
    case 1: return fQDCh; // DQ
    default: er("ETFPlaHit", "GetQChannel:udvh is not 0~3"); break;
  } // end if
  return nullptr; // never gets here, just to suppress compiler warnings
} // end member function GetQChannel

void ETFPlaHit::ConfigureDraw(){
  const int sta = GetFiredStatus();
  if(sta <= 0) return;

  ETFHit::ConfigureDraw();
  fGHit->SetMarkerStyle(28);
  if(4 == sta) fGHit->SetMarkerColor(kRed);
  if(2 == sta) fGHit->SetMarkerColor(kCyan);
  if(1 == sta) fGHit->SetMarkerColor(kBlue);
} // end member function ConfigureDraw

// print the hit info
void ETFPlaHit::Print(const char *) const{
  ETFHit::Print();
  cout << "StripId: " << fStripId << endl;
  cout << "strip length: " << fLength << " mm, v_eff: " << VEFF << " mm/ns" << endl;
  cout << "\033[1mFiredStatus: " << GetFiredStatus() << "\033[0m" << endl;
  cout << "---- Up (Left) End ----" << endl;
  for(int i = 0; i < 2; i++) if(fUCh[i]) fUCh[i]->Print();
  if(fQUCh){
    fQUCh->Print();
    cout << "QDC: " << fQL << endl;
  } // end if
  cout << "---- Down (Right) End ----" << endl;
  for(int i = 0; i < 2; i++) if(fDCh[i]) fDCh[i]->Print();
  if(fQDCh){
    fQDCh->Print();
    cout << "QDC: " << fQR << endl;
  } // end if
  cout << "hit time: " << GetTime() << " ns" << endl;
  cout << "hit pos (w.r.t the det center): ";
  cout << fHitPos << " mm" << endl;
  cout << "sqrt(QL*QR): " << fQ << " QL/QR: " << fQL / fQR << endl;
  cout << "Optimal status: " << fIsOptimal << endl;
} // end member function Print

///< assign those member variables that can only be done after all raw data are assigned
void ETFPlaHit::Assign(){
  // assign fired status //
  AssignFiredStatus();
  // assign hit Q //
  AssignHitQ();
} // end member function Assign

void ETFPlaHit::Filter(double trigLB, double trigHB){
  return; // temporarily not used -- may not be worth it, or even necessary
  if(-999. == trigLB || -999. == trigHB) return; // not designated for filtering
  if(fFiltered) return; // called
  fFiltered = true;
  if(!fUCh[0] || !fDCh[0]) return; // at least either one is not fired

  const double nl = fUCh[0]->GetNofLeadingEdges();
  const double nr = fDCh[0]->GetNofLeadingEdges();
  if(!nl || !nr) return; // either ch is empty

  // 1. pick up those that falls in [fTrigLB, fTriHB], from which select the pair with least l-r //
  auto &vl = const_cast<ETFHPTDCCh *>(fUCh[0])->GetLeadingEdges();
  auto &vr = const_cast<ETFHPTDCCh *>(fDCh[0])->GetLeadingEdges();
  int im = -1, jm = -1; // index of the optimal elements
  // exquisite version: seek the dt-smallest pair //
  // double dt, dtm = 1.e200; // time difference, a temporary variable
  // for(int i = 0; i < nl; i++) if(within(vl[i], trigLB, trigHB)){
  //   for(int j = 0; j < nr; j++) if(within(vr[j], trigLB, trigHB)){
  //     if((dt = fabs(vl[i] - vr[j])) < dtm){
  //       dtm = dt;
  //       im = i;
  //       jm = j;
  //     } // end if
  //   } // end for and if over j
  // } // end for and if over i
  // simpler version
  for(int i = 0; i < nl; i++) if(within(vl[i], trigLB, trigHB)){ im = i; break; }
  for(int i = 0; i < nr; i++) if(within(vr[i], trigLB, trigHB)){ jm = i; break; }

  // 2. push the optimal ones to the front by dropping those lying ahead //
  // and do the same to trailing edges to make the tot right //
  if(im > 0){
    vl.erase(vl.begin(), vl.begin() + im);
    auto &vlt = const_cast<ETFHPTDCCh *>(fUCh[0])->GetTrailingEdges();
    const int imt = size_t(im) < vlt.size() ? im : vlt.size();
    if(imt > 0) vlt.erase(vlt.begin(), vlt.begin() + imt);
  } // end if
  if(jm > 0){
    vr.erase(vr.begin(), vr.begin() + jm);
    auto &vrt = const_cast<ETFHPTDCCh *>(fDCh[0])->GetTrailingEdges();
    const int jmt = size_t(jm) < vrt.size() ? jm : vrt.size();
    if(jmt > 0) vrt.erase(vrt.begin(), vrt.begin() + jmt);
  } // end if
  if(-1 == im) vl.clear();
  if(-1 == jm) vr.clear();
} // end member function Filter

// firedStatus: 0: not fired; 1: U(L) fired; 2: D(R) fired;
// if bothly fired, then, 4: hitPos within reasonable range; 3: otherwise
void ETFPlaHit::AssignFiredStatus(){
  if(fUCh[0] && fUCh[0]->nl()) fTimeL = fUCh[0]->GetLeadingEdge();
  if(fDCh[0] && fDCh[0]->nl()) fTimeR = fDCh[0]->GetLeadingEdge();
  fFiredStatus = (fTimeL > 0.) + (fTimeR > 0.);
  if(2 == fFiredStatus){
    fHitPos = ((fTimeL - fTimeR)*VEFF)/2.; // x in det-local frame
    if(fHitPos > -4.*fLength && fHitPos < 4.*fLength) fFiredStatus = 4; // good hit
    else fFiredStatus = 3;
    fTime = (fTimeL + fTimeR) / 2. - fDelay;
  } // end if
  else{ // singly fired
    fTime = (fTimeL > 0. ? fTimeL : fTimeR) - fDelay;
    if(fTimeL > 0.) fFiredStatus = 1;
    else fFiredStatus = 2;
  } // end else
} // end member function AssignFiredStatus

void ETFPlaHit::AssignHitQ(){
  if(fQUCh && fQUCh->nv()) fQL = fQUCh->value();
  if(fQDCh && fQDCh->nv()) fQR = fQDCh->value();
  if(fQL > 0. && fQR > 0.){
    fQ = sqrt(fQL * fQR);
  } // end if
  else{
    if(fQL > 0.) fQ = fQL;
    if(fQR > 0.) fQ = fQR;
  } // end else
} // end member function AssigHitQ

// pile up analysis
// \param np nof particles \param dtm min dt between trig-gen par and a adjacent par
void ETFPlaHit::PileUpAna(int &np, double &dtm, double trigLB, double trigHB){
  if(!fUCh[0] || !fDCh[0]) return;
  const int nl = fUCh[0]->nl(), nr = fDCh[0]->nl();
  if(!nl || !nr) return;
  np = 0; dtm = 1.e200;
  const double dtB0 = (2*(-4.*fLength)) / VEFF;
  const double dtB1 = (2*(4.*fLength)) / VEFF;
  for(int i = 0; i < nl; i++){
    const double tl = fUCh[0]->tl(i);
    for(int j = 0; j < nr; j++){
      const double tr = fDCh[0]->tl(j);
      const double dt = tl - tr;
      if(within(dt, dtB0, dtB1)){ // a recognized particle passing through
        if(within(tr, trigLB, trigHB) && dtm > fabs(dt)) dtm = fabs(dt);
        np++;
      } // end if
    } // end inner for
  } // end outer for
} // end member function PileUpAna

// average of the tot-s of the two ends
double ETFPlaHit::tot() const{
  if(!fUCh[0] || !fDCh[0]) return -999.;
  const double t0 = fUCh[0]->tot(), t1 = fDCh[0]->tot();
  // if(t0 < 0. || t1 < 0.) return -999.;
  // return (t0 + t1) / 2.;
  if(t0 < 0. && t1 < 0.) return -999.;
  if(t0 > 0. && t1 > 0.) return (t0 + t1) / 2.;
  if(t0 > 0.) return t0;
  return t1;
} // end member function tot

// average of the tot-s of the two ends - the H (SFE) channel
double ETFPlaHit::totH() const{
  if(!fUCh[1] || !fDCh[1]) return -999.;
  const double t0 = fUCh[1]->tot(), t1 = fDCh[1]->tot();
  // if(t0 < 0. || t1 < 0.) return -999.;
  // return (t0 + t1) / 2.;
  if(t0 < 0. && t1 < 0.) return -999.;
  if(t0 > 0. && t1 > 0.) return (t0 + t1) / 2.;
  if(t0 > 0.) return t0;
  return t1;
} // end member function tot

bool ETFPlaHit::qgood() const{
  return fQUCh && fQDCh && fQUCh->good() && fQDCh->good();
} // end member function qgood
